home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PsL Monthly 1993 December
/
PSL Monthly Shareware CD-ROM (December 1993).iso
/
prgmming
/
dos
/
c
/
rcs.exe
/
CACHE.DOC
< prev
next >
Wrap
Text File
|
1992-09-01
|
13KB
|
317 lines
A Simple Reentrant Cache System
Version 1.0
by Philip J. Erdelsky
CompuServe 75746,3411
InterNet 75746.3411@compuserve.com
September 1, 1992
PUBLIC DOMAIN -- NO RESTRICTIONS ON USE
1. Introduction
---------------
Many applications that use disks tend to read and write the same sectors
repeatedly. A technique called caching can make such applications run much
faster. With this technique, frequently used sectors are read into memory
buffers when the application begins. The application then reads and writes the
memory buffers. When the application terminates, the buffers are written to
the disk. Since reading and writing memory buffers is much faster than reading
or writing disk sectors, the application can run much faster. There are some
problems with caching. For example, it can be difficult to determine which
sectors will be used frequently. However, the technique is widely used because
the improvement is often quite substantial.
This cache system is a simple one, designed to be placed between the Reentrant
DOS-Compatible File System (RDCF) and a disk driver.
A single cache can be used for two or more drives, as long as they all have
the same sector size. The cache system is not reentrant with respect to
different drives in this case, so access to it must be serialized.
The cache system can handle as many separate caches as available memory
permits. It is fully reentrant with respect to different caches. However,
separate caches may not access the same drive.
The cache system calls the standard C functions malloc() and free() when a
cache is being initialized or freed, so reentrancy of those functions is
required at those times. However, it does no memory allocation or deallocation
at other times.
The number of sectors in the cache and the size of each sector are determined
at run time and need not be the same for all caches. The only limit to the
number of sectors in the cache is available memory. A cached sector need not
correspond to a single sector on disk, but it must be treated as such by
functions that call and are called by the cache system.
Each cache can handle up to 32,768 drives, numbered from 0 to 32,767, and up to
65,536 sectors on each drive, numbered from 0 to 65,535. The use of larger
numbers will produce numeric overflow and catastrophic failure. Drive numbers
and sector numbers need not be consecutive.
No cache system can be optimal in every conceivable case, no matter how
cleverly it is programmed. This cache system uses one of the simplest methods,
and it is apparently adequate for some purposes. You are welcome to make your
own improvements, but you must assume responsibility for the results if you
make a mistake.
In this cache system, each cached sector may exist in any of three states:
(1) An EMPTY sector bears no relation to any disk sector.
(2) A CLEAN sector contains the same data as the corresponding disk
sector.
(3) A DIRTY sector contains data which is to be written to the
corresponding disk sector but has not yet been written.
The cache system attempts to keep each sector in the cache as long as it can,
and it uses the least-recently-used criterion when cached sectors must be
reused. The process is explained in greater detail in Section 4.
The source code for the cache system consists of the file CACHE.C and the
header file CACHE.H. The latter should be #included in any source file that
calls on the cache system, because it contains prototypes for all cache
functions and other necessary information.
The cache system calls on the following standard C functions:
malloc()
free()
memcpy()
To prevent identifier conflicts, all publicly defined identifiers in the cache
system begin with the characters "cache", "CACHE" or "_CACHE".
2. How the Cache Reads and Writes a Sector
------------------------------------------
The cache system does not read or write the disk directly. You must provide it
with a pointer to a function that you have written for your implementation. The
cache system then calls this function whenever it needs to read or write a disk
sector. The format of the function call is as follows:
e = (*drive_access)(write, drive, sector, buffer);
int write; nonzero (true) for a write operation; zero (false)
for a read operation
unsigned drive; drive to be read or written
unsigned sector; sector to be read or written
void *buffer; address of memory buffer to receive or supply data
unsigned e; zero for a successful operation, or an
implementation-defined nonzero error code
When the cache receives a nonzero value from this function, it aborts the cache
operation and returns the value as its own functional value. The cache control
block contains the drive and sector numbers of the offending operation in the
members error_drive and error_sector, respectively. This is especially helpful
for diagnostic purpose, because the error almost always occurs in a sector
other than the one currently being accessed by the application.
3. Creating and Initializing a Cache
------------------------------------
The following function call creates and initializes a cache:
q = cache_initialize(drive_access, number_of_sectors, sector_size);
unsigned (*drive_access)(); pointer to function called by cache
to read or write a sector
unsigned number_of_sectors; number of sectors to be stored in cache
(must be at least 1)
unsigned sector_size; number of bytes in a sector (1-65,535)
struct cache *q; pointer to cache control block, or NULL
if there was insufficient memory
The function calls on malloc() to allocate memory for the cache storage. If
malloc() returns NULL at any point in the process, the function calls free()
to release any memory that it has allocated and returns a NULL pointer.
Otherwise, it returns a pointer that can be passed to other cache functions.
It marks all sectors in the cache as empty.
A sector size near the upper limit of 65,535 may cause numeric overflow and
catastrophic failure if the implementation does not permit allocation of single
objects larger than 65,535 bytes.
4. Reading or Writing Through the Cache
---------------------------------------
The heart of the cache system is the following function call:
e = cache_access(q, write, drive, sector, buffer);
struct cache *q; pointer to cache control block received from
cache_initialize()
int write; nonzero (true) for a write operation; zero (false)
for a read operation
unsigned drive; drive to be read or written
unsigned sector; sector to be read or written
void *buffer; address of memory buffer to receive or supply data
unsigned e; zero for a successful operation, or an
implementation-defined nonzero error code
This function writes or reads the specified sector to or from the specified
drive, using the cache as follows:
(1) If the specified sector is not already in the cache, a cache sector
is assigned to it as follows:
(a) If there are still empty sectors in the cache, one of them is
assigned to the specified sector.
(b) If there are no empty sectors, but there are some clean ones,
the least-recently used clean sector is marked as empty and
assigned to the specified sector.
(c) In other cases, the least-recently used dirty sector is written
to disk, marked as empty, and assigned to the specified sector.
(2) When reading,
(a) If the sector is empty, it is read f